#pragma once
#include "../Common/Common.h"

#define  DEFAULT_BUFFER_SIZE 1024
#define  MAX_STRING_SIZE 0xFFFF

struct ArrayBufferError
{

};

class OArrayBuffer
{
public:
	explicit OArrayBuffer(size_t s = DEFAULT_BUFFER_SIZE) : 
		m_buffer(s,0),m_current_size(0)
	{
	}

	~OArrayBuffer()
	{
	}

	void write(uint8 v)
	{
		writeType(v);
	}

	void write(int8 v)
	{
		writeType(v);
	}

	void write(uint16 v)
	{
		writeType(v);
	}

	void write(int16 v)
	{
		writeType(v);
	}

	void write(uint32 v)
	{
		writeType(v);
	}

	void write(int32 v)
	{
		writeType(v);
	}

	void write(uint64 v)
	{
		writeType(v);
	}

	void write(int64 v)
	{
		writeType(v);
	}

	void write(float v)
	{
		writeType(v);
	}

	void write(double v)
	{
		writeType(v);
	}

	void write(const std::string& v)
	{
		ASSERT(v.size() <= MAX_STRING_SIZE);
		write(static_cast<uint16>(v.size()));
		if (!v.empty())
			writeBytes(v.data(),v.size());
	}

	size_t size() const
	{
		return m_current_size;
	}

	void clear()
	{
		m_current_size = 0;
	}

	const uint8* data() const
	{
		return &m_buffer[0];
	}

	std::vector<uint8>::iterator begin()
	{
		return m_buffer.begin() + m_current_size;
	}

	std::vector<uint8>::iterator end()
	{
		return m_buffer.end();
	}

	void writeBytes(const void* bs, size_t s)
	{
		writeBytes((const uint8*)bs, s);
	}

	void writeBytes(const uint8* bs, size_t s)
	{
		if (m_buffer.size() - m_current_size < s)
		{
			m_buffer.resize(m_buffer.size() + s);
		}

		uint8* b = &m_buffer[m_current_size];
		memcpy(b,bs,s);
		m_current_size += s;
	}

protected:
	template<typename T>
	void writeType(const T& v)
	{
		if (m_buffer.size() - m_current_size < sizeof(T))
		{
			m_buffer.resize(m_buffer.size() + sizeof(T));
		}

		T* b = reinterpret_cast<T*>(&m_buffer[m_current_size]);
		*b = v;

		m_current_size += sizeof(T);
	}

private:
	size_t m_current_size;
	std::vector<uint8> m_buffer;
};

template<typename T>
OArrayBuffer& operator << (OArrayBuffer& ob, const T& value)
{
	ob.write(value);
	return ob;
}

class IArrayBuffer
{
public:
	explicit IArrayBuffer(const uint8* buffer, size_t s, bool is_own=false) : 
		m_buffer(buffer),m_size(s), m_current_readp(0),m_is_own(is_own)
	{
	}

	~IArrayBuffer()
	{
		if (m_is_own)
			delete []m_buffer;
	}

	void read(uint8& v)
	{
		readType(v);
	}

	void read(int8& v)
	{
		readType(v);
	}

	void read(uint16& v)
	{
		readType(v);
	}

	void read(int16& v)
	{
		readType(v);
	}

	void read(uint32& v)
	{
		readType(v);
	}

	void read(int32& v)
	{
		readType(v);
	}

	void read(uint64& v)
	{
		readType(v);
	}

	void read(int64& v)
	{
		readType(v);
	}

	void read(float& v)
	{
		readType(v);
	}

	void read(double& v)
	{
		readType(v);
	}

	void read(std::string& v)
	{
		v.clear();

		uint16 s = 0;
		read(s);

		if (s <= 0)
			return;

		v.resize(s);
		int8* b = &*v.begin();
		readBytes(b,s);
	}

	size_t size() const
	{
		return m_size - m_current_readp;
	}

	void clear()
	{
		m_current_readp = 0;
	}

	typedef const uint8* iterator;

	iterator begin()
	{
		return m_buffer + m_current_readp;
	}

	iterator end()
	{
		return m_buffer + m_size;
	}

	void readBytes(void* bs, size_t s)
	{
		readBytes((uint8*)bs, s);
	}

	void readBytes(uint8* bs, size_t s)
	{
		if (m_size - m_current_readp < s)
		{
			ASSERT(false);
			throw ArrayBufferError();
		}

		const uint8* b =&*begin();
		memcpy(bs,b,s);
		m_current_readp += s;
	}

protected:
	template<typename T>
	void readType(T& v)
	{
		if (m_size - m_current_readp < sizeof(T))
		{
			ASSERT(false);
			throw ArrayBufferError();
		}

		const T* b = reinterpret_cast<const T*>(&*begin());
		v = *b;
		m_current_readp += sizeof(T);
	}

private:
	size_t m_current_readp;
	const uint8* m_buffer;
	size_t m_size;
	bool m_is_own;

};

template<typename T>
IArrayBuffer& operator >> (IArrayBuffer& ob, T& value)
{
	ob.read(value);
	return ob;
}